Spring HandlerExceptionResolver
的实现用来处理在控制器执行时发生的意外的异常。HandlerExceptionResolver
有些类似于你在web程序的web.xml
中定义了异常的映射。但是,它提供了一种更灵活的做法。比如他们提供了哪个处理器在执行时抛出异常的信息。此外,编码的方式去处理异常给了你在请求转发到另一个URL前更多机会选择合适的响应(和使用Servlet特定的异常映射有相同的结果)。
除了实现HandlerExceptionResolver
接口(关键是实现resolveException(Exception, Handler)
方法和返回一个ModelAndView
),你还可以使用Spring提供的SimpleMappingExceptionResolver
或是创造一个带@ExceptionHandler
注解的方法。SimpleMappingExceptionResolver
使你可以将任何可能抛出的异常类名映射到一个视图。这和Servlet API的异常映射是一样的,但你也可以实现对来自不同处理器的异常更细粒度的映射。另一方面,@ExceptionHandler
注释可以用于处理异常的方法上。这样的方法可以被定义在@Controller
内本地使用或是定义在@ControllerAdvice
内应用到多个@Controller
上。下面一节会解释更多的细节。
HanlderExceptionResolver
接口和SimpleMappingExceptionResolver
实现允许你在映射异常到特定的视图时,可以在映射到视图前选择声明一些Java逻辑。然而,某些情况下,特别是依赖@ResponseBody
方法而不是视图解析时,直接设置响应的状态并选择性的写一些错误到响应体中或更便捷。
你可以通过@ExceptionHandler
方法实现这点。当控制器内声明这样的方法,同个控制器(或是它的子类)中的@RequestMapping
方法引发了异常时,该方法会被应用。你也可以将@ExceptionHandler
方法声明在@ControllerAdvice
类内部,这样它会处理多个控制器@RequestMapping
方法的异常,下面的例子是一个控制器内的@ExceptionHandler
方法:
@Controller
public class SimpleController {
// @RequestMapping methods omitted ...
@ExceptionHandler(IOException.class)
public ResponseEntity<String> handleIOException(IOException ex) {
// prepare responseEntity
return responseEntity;
}
}
@ExceptionHandler
的值可以设置为异常类型的数组。如果抛出的异常符合这个列表中的异常类型,那么被注释@ExceptionHandler
的方法会被调用。如果没有设置异常类型的列表,那么会使用方法参数的异常类型。
对于
@ExceptionHandler
方法,无论该方法是咋特定的控制器还是advice bean中,根异常匹配都将优先于当前异常原因的匹配。但是对于一个更高优先级的@ControllerAdvice
仍然会优先于其他低优先级的advice bean中的匹配(无论是根还是原因的等级)。因此,当使用多个advice时,请以异常相对应的顺序声明映射的advice bean顺序!
与带@RequestMapping
注解的标准的控制器方法相似,@ExceptionHandler
方法的参数和返回值也可以很灵活。比如,在Servlet环境中可以访问HttpServletReqeust
,而在Portlet环境中可以访问PortletRequest
。返回类型可以是一个String
,被用来检测视图名;可以是一个ModelAndView
对象;一个ResponseEntity
;甚至你可以添加@ResponseBody
让返回值通过消息转换器转换并写入响应流。
Spring MVC可能会在处理请求的时候引发一系列的异常。SimpleMappingExceptionResolver
可以轻易的将任意异常映射到默认错误视图。然而,当使用自动解析响应的客户端时,你会希望直接设置特定的响应状态码。状态码会根据抛出的异常被设置成客户端错误(4XX)或是服务器错误(5XX)。
DefaultHandlerExceptionResolver
会将Spring MVC的异常转换成特定的错误状态码。它默认注册到被MVC命名空间,MVC Java配置和DispatcherServlet
(即无论是使用MVC命名空间还是Java配置)。下面列出了这个解析器处理的异常及对应的状态码:
Exception | HTTP Status Code |
---|---|
BindException |
400(Bad Request) |
ConversionNotSupportedException |
500(Internal Server Error) |
HttpMediaTypeNotAcceptableException |
406(Not Acceptable) |
HttpMediaTypeNotSupportedException |
415(Unsupported Media Type) |
HttpMessageNotReadableException |
400(Bad Request) |
HttpMessageNotWritableException |
500(Internal Server Error) |
HttpRequestMethodNotSupportedException |
405(Method Not Allowed) |
MethodArgumentNotValidException |
400(Bad Request) |
MissingPathVariableException |
500(Internal Server Error) |
MissingServletRequestParameterException |
400(Bad Request) |
MissingServletRequestPartException |
400(Bad Request) |
NoHandlerFoundException |
404(Not Found) |
NoSuchRequestHandlingMethodException |
404(Not Found) |
TypeMismatchException |
400(Bad Request) |
DefaultHandlerExceptionResolver
透明的设置响应状态。但是,当你的应用程序需要添加对开发者友好的内容到响应中去时(比如提供REST API时),它不会将任何的错误内容写入到响应体。你可以准备一个ModelAndView
并通过视图解析渲染错误内容——即,通过配置ContentNegotiatingViewResolver
,MappingJackson2JsonView
等等。然而,你可能还是更喜欢使用@ExceptionHandler
方法代替。
如果你更喜欢通过@ExceptionHandler
写入错误内容,你可以继承ResponseEntityExceptionHandler
。这是个继承自带@ControllerAdvice
的类并提供了@ExceptionHandler
方法来处理异常并返回ResponseEntity
。它使你可以自定义响应并通过消息转换器写入错误内容。更多信息见ResponseEntityExceptionHandler
。
业务逻辑的异常可以带@ResponseStatus
。当这个产生异常时,ResponseStatusExceptionResolver
会将响应设为响应的状态值。DispatcherServler
默认会注册ResponseStatusExceptionResolver
,因此它是可用的。
当响应的状态被设置成错误状态码且响应体为空时,Servlet容器通常会渲染格式化的HTML错误页。为了自定义容器的默认错误页,你可以在web.xml
中声明<error-page>
属性。在Servlet 3之前,这个元素不得不映射特定的状态或是异常。从Servelt 3开始,错误页面不再需要被映射,这实际上意味着指定的位置会自定义默认的Servlet容器错误页面。
<error-page>
<location>/error</location>
</error-page
注意,错误页面的实际位置可以使一个JSP或是容器内通过@Controller
方法映射的URL:当写错误信息是,HttpServletResponse
中设置的错误状态码和错误消息可以在控制器中通过请求的属性获得:
@Controller
public class ErrorController {
@RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
或是使用JSP:
<%@ page contentType="application/json" pageEncoding="UTF-8"%>
{
status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
reason:<%=request.getAttribute("javax.servlet.error.message") %>
}